﻿using System;
using System.Collections.Generic;
using MathExpression;

namespace SimplexSolver
{
    public class SimplexTable
    {
        public int ColumnsCount
        {
            get { return _matrix[0].Length; }
        }

        public int RowsCount
        {
            get { return _matrix.Length; }
        }

        public string[] ColumnHeaders { get; set; }

        public string[] RowHeaders { get; set; }

        public IMathExpression[][] RestrictionMatrix
        {
            get
            {
                int rowCount = _matrix.Length - 1;
                int colCount = _matrix[0].Length - 1;
                var restrictionMatrix = new IMathExpression[rowCount][];
                for (int i = 0; i < rowCount; i++)
                {
                    restrictionMatrix[i] = new IMathExpression[colCount];
                    for (int j = 0; j < colCount; j++)
                    {
                        restrictionMatrix[i][j] = _matrix[i + 1][j + 1].Clone();
                    }
                }
                return restrictionMatrix;
            }
        }

        public IMathExpression[] TargetRow
        {
            get
            {
                int length = _matrix[0].Length - 1;
                var targetRow = new IMathExpression[length];
                for (int i = 0; i < length; i++)
                {
                    targetRow[i] = _matrix[0][i + 1].Clone();
                }
                return targetRow;
            }
        }

        public IMathExpression[] FreeValueColumn
        {
            get
            {
                int length = _matrix.Length - 1;
                var freeValueColumn = new IMathExpression[length];
                for (int i = 0; i < length; i++)
                {
                    freeValueColumn[i] = _matrix[i + 1][0].Clone();
                }
                return freeValueColumn;
            }
        }

        public IMathExpression TargetValue
        {
            get { return _matrix[0][0].Clone(); }
        }

        private IMathExpression[][] _matrix;

        public SimplexTable(IList<IMathExpression> target, IList<IMathExpression> freeValues,
                            IList<IMathExpression[]> restrictionMatrix, IMathExpression value = null)
        {
            int rowCount = restrictionMatrix.Count + 1;
            int colCount = restrictionMatrix[0].Length + 1;
            InitializeMatrix(target, freeValues, restrictionMatrix, rowCount, colCount);
            InitializeHeaders(rowCount, colCount);
            SetValue(value);
        }

        private void SetValue(IMathExpression value)
        {
            if (value != null)
            {
                _matrix[0][0] = value.Clone();
            }
        }

        private void InitializeMatrix(IList<IMathExpression> target, IList<IMathExpression> freeValues,
                                      IList<IMathExpression[]> restrictionMatrix, int rowCount, int colCount)
        {
            _matrix = new IMathExpression[rowCount][];
            for (int i = 0; i < rowCount; i++)
            {
                _matrix[i] = new IMathExpression[colCount];
                for (int j = 0; j < colCount; j++)
                {
                    if (i == 0 && j == 0)
                    {
                        continue;
                    }
                    IMathExpression value = GetTableValue(target, freeValues, restrictionMatrix, i, j);
                    _matrix[i][j] = value.Clone();
                }
            }
            _matrix[0][0] = ExpressionFactory.Create(0.0);
        }

        private IMathExpression GetTableValue(IList<IMathExpression> target, IList<IMathExpression> freeValues,
                                              IList<IMathExpression[]> restrictionMatrix, int i, int j)
        {
            IMathExpression value;
            if (i == 0 && j == 0)
            {
                return null;
            }
            if (i == 0)
            {
                value = target[j - 1].Clone();
            }
            else if (j == 0)
            {
                value = freeValues[i - 1].Clone();
            }
            else
            {
                value = restrictionMatrix[i - 1][j - 1].Clone();
            }
            return value;
        }

        private void InitializeHeaders(int rowCount, int colCount)
        {
            ColumnHeaders = new string[colCount];
            ColumnHeaders[0] = "b";
            RowHeaders = new string[rowCount];
            RowHeaders[0] = "L";
        }

        /// <summary>
        /// Racalculate Simplex Table by selected item indexes
        /// </summary>
        /// <param name="rowIndex">Index starts from target function (must be more than zero)</param>
        /// <param name="columnIndex">Index starts from free values column (must be more than zero)</param>
        /// <exception cref="IndexOutOfRangeException">Thrown when row or column index is incorrect</exception>
        /// <exception cref="ArgumentException">Thrown when item for recalculating is less or equal to zero</exception>
        public void Recalculate(int rowIndex, int columnIndex)
        {
            CheckIndexes(rowIndex, columnIndex);
            RecalculateMatrix(rowIndex, columnIndex);
            RecalculateSelectedRow(rowIndex, columnIndex);
            RecalculateSelectedColumn(rowIndex, columnIndex);
            InvertSelectedItem(rowIndex, columnIndex);
            ChangeHeaders(rowIndex, columnIndex);
        }

        /// <summary>
        /// Check correct indexes and items for recalculating.
        /// Indexes must be more than zero and less than variables count.
        /// Item for recalculating must be positive only
        /// </summary>
        /// <param name="rowIndex">Matrix row index for recalculating</param>
        /// <param name="columnIndex">Matrix column index for recalculating</param>
        /// <exception cref="IndexOutOfRangeException">Thrown when row or column index is incorrect</exception>
        /// <exception cref="ArgumentException">Thrown when item for recalculating is less or equal to zero</exception>
        private void CheckIndexes(int rowIndex, int columnIndex)
        {
            if (rowIndex <= 0 ||
                rowIndex >= _matrix.Length ||
                columnIndex <= 0 ||
                columnIndex >= _matrix[0].Length)
            {
                throw new IndexOutOfRangeException("Incorrect indexes of simplex table for recalculating");
            }
            var data = _matrix[rowIndex][columnIndex].Data as ScalarData;
            if (data != null && data.Value <= 0.0)
            {
                throw new ArgumentException("Item for recalculating must be positive only");
            }
        }

        private void ChangeHeaders(int rowIndex, int columnIndex)
        {
            string headerFromColumn = ColumnHeaders[columnIndex];
            string headerFromRow = RowHeaders[rowIndex];
            ColumnHeaders[columnIndex] = headerFromRow;
            RowHeaders[rowIndex] = headerFromColumn;
        }

        private void InvertSelectedItem(int rowIndex, int columnIndex)
        {
            IMathExpression oldValue = _matrix[rowIndex][columnIndex].Clone();
            _matrix[rowIndex][columnIndex] = oldValue.Reverse();
        }

        private void RecalculateSelectedColumn(int rowIndex, int columnIndex)
        {
            IMathExpression denominator = _matrix[rowIndex][columnIndex];
            IMathExpression minus = ExpressionFactory.Create(-1.0);
            int rowCount = _matrix.Length;
            for (int i = 0; i < rowCount; i++)
            {
                if (i == rowIndex)
                {
                    continue;
                }
                IMathExpression result = _matrix[i][columnIndex].Divide(denominator);
                _matrix[i][columnIndex] = result.Multiply(minus);
            }
        }

        private void RecalculateSelectedRow(int rowIndex, int columnIndex)
        {
            IMathExpression denominator = _matrix[rowIndex][columnIndex];
            int columnCount = _matrix[0].Length;
            for (int i = 0; i < columnCount; i++)
            {
                if (i == columnIndex)
                {
                    continue;
                }
                _matrix[rowIndex][i] = _matrix[rowIndex][i].Divide(denominator);
            }
        }

        private void RecalculateMatrix(int rowIndex, int columnIndex)
        {
            IMathExpression selectedValue = _matrix[rowIndex][columnIndex].Clone();
            int rowCount = _matrix.Length;
            int columnCount = _matrix[0].Length;

            for (int i = 0; i < rowCount; i++)
            {
                if (i == rowIndex)
                {
                    continue;
                }
                for (int j = 0; j < columnCount; j++)
                {
                    if (j == columnIndex)
                    {
                        continue;
                    }
                    IMathExpression value1 = selectedValue.Multiply(_matrix[i][j]);
                    IMathExpression value2 = _matrix[i][columnIndex].Multiply(_matrix[rowIndex][j]);
                    IMathExpression value3 = value1.Subtract(value2);
                    IMathExpression result = value3.Divide(selectedValue);
                    _matrix[i][j] = result;
                }
            }
        }

        public SimplexTable Clone()
        {
            var table = new SimplexTable(TargetRow, FreeValueColumn, RestrictionMatrix, _matrix[0][0])
                            {RowHeaders = RowHeaders, ColumnHeaders = ColumnHeaders};
            return table;
        }

        public IMathExpression[] GetSolutionVector(int varsCount)
        {
            var solutionX = new IMathExpression[varsCount];
            for (int i = 0; i < varsCount; i++)
            {
                solutionX[i] = ExpressionFactory.Create(0.0);
            }
            IMathExpression[] freeValues = FreeValueColumn;
            string[] header = RowHeaders;
            int count = RowsCount;
            for (int i = 1; i < count; i++)
            {
                if (header[i][0] == 'x')
                {
                    string sIndex = header[i].Substring(1);
                    int index;
                    if (!int.TryParse(sIndex, out index))
                    {
                        throw new Exception("Некорректный индекс");
                    }
                    solutionX[index] = freeValues[i - 1];
                }
            }
            return solutionX;
        }
    }
}